<?php
defined('IN_MY_PHP') || die(0);

// 初始化本文件单独使用时涉及到的常量
defined('CRLF') || define('CRLF', "\r\n");
defined('DS') || define('DS', DIRECTORY_SEPARATOR);
defined('UPLOAD_PATH') || define('UPLOAD_PATH',rtrim(preg_replace('/\\\\\\//', DS, $_SERVER['DOCUMENT_ROOT']), DS) . DS . 'uploads' .DS);

/**
 * 工具类（不含有具体的业务代码）.
 *
 * @author netmou <leiyanfo@sina.com>
 *
 */
class Utility
{
	/**
	 * 获取Excel文件单个Sheet内容
	 */
	public function readExcelFile($fileName, $rowStart = 1, $sheetIndex = 0)
	{
		set_time_limit(0);
		$result = array();
		try {
			$readType = PHPExcel_IOFactory::identify($fileName);  //在不知道文档类型的情况下获取
			$excelReader = PHPExcel_IOFactory::createReader($readType);
			$objPHPExcel = $excelReader->load($fileName);
			$objWorksheet = $objPHPExcel->getSheet($sheetIndex);
			$highestRow = $objWorksheet->getHighestRow();
			$highestColumn = $objWorksheet->getHighestColumn();
			$highestColumnIndex = PHPExcel_Cell::columnIndexFromString($highestColumn);
			for ($row = $rowStart; $row <= $highestRow; $row++) {
				for ($col = 0; $col <= $highestColumnIndex; $col++) {
					$cell = $objWorksheet->getCellByColumnAndRow($col, $row);
					$value = $cell->getValue();
					if ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_NUMERIC) {
						$formatcode = $objWorksheet->getParent()->getCellXfByIndex($cell->getXfIndex())
							->getNumberFormat()->getFormatCode();
						if ($formatcode != null && strtolower($formatcode) != 'general') {
							$value = PHPExcel_Style_NumberFormat::toFormattedString($value, $formatcode);
						}
					} elseif ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA) {
						$value = $cell->getCalculatedValue();
					}
					$result[$row - $rowStart][$col] = trim($value);
				}
			}
		} catch (Exception $e) {
			trigger_error('File can\'nt be parsed by PHPExcel !', E_USER_ERROR);
		}
		
		return $result;
	}
	
	/**
	* 将二维数组的内容写入到excel文件并下载(文件格式仅限xls)
	*/
	public function writeToExcel($data, $filename = 'data.xls'){
		set_time_limit(0);
		$phpexcel = new PHPExcel();
		$phpexcel->setActiveSheetIndex(0);
		$activeSheet = $phpexcel->getActiveSheet();
		$rowIndex = 1;
		foreach($data as $record){
			$colIndex = 0;
			foreach ($record as $val) {
				$colLetter = PHPExcel_Cell::stringFromColumnIndex($colIndex);
				$activeSheet->setCellValueExplicit($colLetter . $rowIndex, $val, PHPExcel_Cell_DataType::TYPE_STRING);
				++$colIndex;
			}
			++$rowIndex;
		}
		$filename = str_replace("+", "%20", urlencode($filename));
		
		header("Cache-Control: private, must-revalidate");
		header("Content-Type: application/octet-stream");
		header("Content-Disposition: attachment;filename={$filename}");
		header("Content-Transfer-Encoding: binary");
		$objWriter = PHPExcel_IOFactory::createWriter($phpexcel, 'Excel5');
		ob_end_clean();
		$objWriter->save("php://output");
		exit;
	}
	
	/**
	* 将数组里的内容进行编码转换
	*/
	public function iconv($in_charset, $out_charset, $data){
		if(is_array($data)){
			foreach($data as $key=>&$val){
				$data[$key] = $this->iconv($in_charset, $out_charset, $val);
			}
			return $data;
		}else{
			return iconv($in_charset, $out_charset, $data);
		}
	}
	
	/**
	 * 通过下面规则检查页面是否能访问
	 * foo/bar-baz-qux.*.php
	 * foo/bar-baz.*.php
	 * foo/bar-baz.php
	 */
	public function accessAllowedCheck($specPath = null, $quit = true){
		static $allowedPages = null;
		
		$store = new Session();
		if($store->get('uid')!=null && $store->get('admin') != 1){
			if($allowedPages == null){
				$pdo = new PDOMySQLDriver();
				$temp = $pdo->fetchData("select `link` from `manage_module_info` where id in ({$store->get('permit')}) and `link` != ''");
				$allowedPages = $this->arrayColumn($temp, 'link');
			}
			if($specPath == null){
				$pathInfo = explode('.', $_SERVER['SCRIPT_NAME']);
				$path =  $pathInfo[0];
			}else{
				$path = $specPath;
			}
			$checkResult = false;
			$path = preg_quote(str_replace(DS, '/', $path), '/');
			foreach($allowedPages as &$page){
				if(preg_match('/'.$path.'\\./is', $page)){
					$checkResult = true;
					break;
				}
			}
			if($checkResult == false && $specPath == null){
				$this->quit('抱歉，您无权限访问此页面！');
			}
			return $checkResult;
		}
		return true;
	}

	/**
	 * 返回地球上两个经纬坐标之间的的距离，算法基于椭圆，返回值单位：米（M）.
	 */
	public function getFlatternDistance($lat1, $lng1, $lat2, $lng2)
	{
		if ($lat1 == $lat2 && $lng1 == $lng2) {
			return 0;
		}
		$f = ($lat1 + $lat2) / 2 * pi() / 180.0;
		$g = ($lat1 - $lat2) / 2 * pi() / 180.0;
		$l = ($lng1 - $lng2) / 2 * pi() / 180.0;

		$sin_g = sin($g);
		$sin_l = sin($l);
		$sin_f = sin($f);

		$a = 6378137.0;
		$fl = 1 / 298.257;

		$sin_g_2 = $sin_g * $sin_g;
		$sin_l_2 = $sin_l * $sin_l;
		$sin_f_2 = $sin_f * $sin_f;

		$s = $sin_g_2 * (1 - $sin_l_2) + (1 - $sin_f_2) * $sin_l_2;
		$c = (1 - $sin_g_2) * (1 - $sin_l_2) + $sin_f_2 * $sin_l_2;

		$w = atan(sqrt($s / $c));
		$r = sqrt($s * $c) / $w;
		$d = 2 * $w * $a;
		$h1 = (3 * $r - 1) / 2 / $c;
		$h2 = (3 * $r + 1) / 2 / $s;

		return $d * (1 + $fl * ($h1 * $sin_f_2 * (1 - $sin_g_2) - $h2 * (1 - $sin_f_2) * $sin_g_2));
	}

	/**
	 * 返回地球上两个经纬坐标之间的的方位角（PAB）.
	 * 其中lat_a, lng_a是A的纬度和经度；lat_b, lng_b是B的纬度和经度，P为北极点
	 * 参考：http://bbs.godeyes.cn/Upload/2006/12/06/205631.rar
	 */
	public function getAzimuth($lat_a, $lng_a, $lat_b, $lng_b) {
		$d = 0;

		$lat_a = $lat_a * pi() / 180;
		$lng_a = $lng_a * pi() / 180;
		$lat_b = $lat_b * pi() / 180;
		$lng_b = $lng_b * pi() / 180;

		$d = sin($lat_a) * sin($lat_b) + cos($lat_a) * cos($lat_b) * cos($lng_b - $lng_a);
		$d = sqrt(1 - $d * $d);
		$d = cos($lat_b) * sin($lng_b - $lng_a) / $d;
		$d = asin($d) * 180 / pi();

		return $d;
}

	/**
	 * 判断点是否在多边形内,算法基于多边形外的点与多边形的边相交，有偶数个交点.
	 */
	public function pointInPolygon($p, $points)
	{
		$cross = 0;
		$size = count($points);
		for ($i = 0; $i < $size; ++$i) {
			$p1 = $points[$i];
			$p2 = $points[($i + 1) % $size];
			if ($p1['lat'] == $p2['lat']) {
				continue;
			}
			if ($p['lat'] < min($p1['lat'], $p2['lat'])) {
				continue;
			}
			if ($p['lat'] >= max($p1['lat'], $p2['lat'])) {
				continue;
			}
			$x = ($p['lat'] - $p1['lat']) * ($p2['lng'] - $p1['lng']) / ($p2['lat'] - $p1['lat']) + $p1['lng'];
			if ($x > $p['lng']) {
				++$cross;
			}
		}

		return $cross % 2 == 1;
	}

	/**根据身份证获取年龄*/
	public function fetchAgeByIdCard($id_card)
	{
		if (!preg_match('/^\\d{17}[\\dx]$/i', $id_card)) {
			return 0;
		}
		$birth = strtotime(substr($id_card, 6, 8));

		return ceil((time() - $birth) / 86400 / 365);
	}

	/**
	 * 去除XSS（跨站脚本攻击）的函数
	 */
	public function removeXSS($val)
	{
		if(!is_scalar($val)){
			return '';
		}
		// #x9 | #xA | #xD | [#x20-#xD7FF] ...  compatibility characters
		// 过滤掉非法字符(删除ASCII中部分不可见字符，回车、换行及水平制表等字符被保留)
		$val = preg_replace('/([\x00-\x08\x0b-\x0c\x0e-\x19])/', '', $val);
		
		// 将内容中的16进和十进制html编码的ASCII字符进行还原
		$search = 'abcdefghijklmnopqrstuvwxyz';
		$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$search .= '1234567890!@#$%^&*()';
		$search .= '~`";:?+/={}[]-_|\'\\';
		for ($i = 0; $i < strlen($search); ++$i) {
			$val = preg_replace('/(&#x0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val);
			$val = preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/', $search[$i], $val);
		}
		
		// 内容中含有如下可能引入外部文件的标签应过滤掉
		$externSet = array('script', 'link', 'style', 'embed', 'object', 'iframe', 'frame', 'xml');
		foreach($externSet as $extern){
			$val = preg_replace('/<'.$extern.'(\\s+.+?)?>/is', '<xss-' . $extern . '>', $val);
			$val = preg_replace('/<\\/'.$extern.'>/is', '</xss-' . $extern . '>', $val);
		}
		
		// 内容中如含有JS事件属性的标签应过滤掉
		$val = preg_replace('/<(\\w+)(\\s+[^>]+?)?\\s+on\\w+\\s*=.+?>/is', '<$1 xss-js-event >', $val);
		
		// 链接中如含有script代码应过滤掉
		$val = preg_replace('/<(\\w+)\\s+[^>]+?javascript:.+?>/is', '<$1 xss-js-fake-statement >', $val);
		
		return $val;
	}

	/**
	 * 本函数针对外部输入数据，递归过滤XSS攻击，SQL转义
	 * 本函数应用在数据保存时，主要防止sql注入、XSS攻击.
	 */
	public function inputData($input = null, $removemXss = true, $addSlash = false)
	{
		if(func_num_args() == 0){
			$input = $_REQUEST;
		}
		//addSlash方法支持递归，优先处理
		if ($addSlash) {
			$input = $this->addSlash($input);
		}
		//removemXss不支持递归，若输入源是数组则需要手动递归；
		if ($removemXss && is_array($input)) {
			foreach ($input as $key => &$val) {
				//func::addSlash已处理完，避免重复转义，注意递归参数
				$input[$key] = $this->{__FUNCTION__}($val, true, false);
			}

			return $input;
		}
		if ($removemXss) {
			$input = $this->removeXSS($input);
		}

		return $input;
	}
	/**
	 * 本函数不同于XSS攻击过滤，本函数仅作HTML转义处理，同样能防止过滤XSS攻击
	 * 本函数应用在数据输出时，主要防止数据影响页面代码.
	 */
	public function outputData($input=null, $stripSlash = false)
	{
		if(func_num_args() == 0){
			$input = $_REQUEST;
		}
		
		//stripSlash方法支持递归，优先处理
		if ($stripSlash) {
			$input = $this->stripSlash($input);
		}

		if (is_array($input)) {
			foreach ($input as $key => &$val) {
				//func::stripSlash已处理完，避免重复反转义，注意递归参数
				$input[$key] = $this->{__FUNCTION__}($val, false);
			}

			return $input;
		}

		return htmlspecialchars($input);
	}

	/**
	 * 过滤字符串中的空白字符（33个ascii字符，不会对utf8造成破坏）.
	 */
	public function removeInvisibleChar($str, $except=array(9, 10, 13)){
		$retStr = '';
		for($i=0; $i < strlen($str); $i++){
			$ascii = ord($str[$i]);
			// 33个不可见字符(0~31,127),默认保留回车(CR:13)、换行（LF:10）、水平制表（TAB:9）等字符
			if(($ascii < 32 || $ascii == 127) && !in_array($ascii, $except)){
				continue;
			}
			$retStr .= sprintf("%02x", $ascii);
		}
		return pack("H*", $retStr);
	}

	/**
	 * 本函数检查指定的URL是否可访问，需要PHP开启CURL.
	 */
	public function curlAvailable($url)
	{
		$curl = curl_init($url);
		curl_setopt($curl, CURLOPT_NOBODY, true); // 不取回数据
		$rst = curl_exec($curl); // 发送请求
		if ($rst !== false && curl_getinfo($curl, CURLINFO_HTTP_CODE) == 200) {
			return true;
		}
		curl_close($curl);

		return false;
	}

	/**
	 * 本函数检查指定的URL是否可访问.
	 */
	public function urlAvailable($url)
	{
		$heads = @get_headers($url);
		if (stristr($heads[0], '200') && stristr($heads[0], 'OK')) {
			return true;
		}

		return false;
	}

	/**
	 * 临时重置页面，建议应用此函数时开启输出缓冲.
	 */
	public function redirect($url)
	{
		if (!$this->headerSent(true)) {
			header('HTTP/1.1 302 Temporarily Moved');
			header('location: '.$url);
			exit(0);
		}
	}

	/**
	 * 给出js-Alert提示并跳转页面.
	 */
	public function jsAlert($msg, $go = '', $charset='utf-8')
	{
		$this->initOutPut();
		echo "<html><head><meta charset=\"{$charset}\" /></head><body>";
		echo "<script type=\"text/javascript\">";
		$msg = $this->toJsVar($msg, '"', false);
		echo "alert({$msg});";
		if (is_int($go)) {
			echo "window.history.go({$go});";
		} elseif (is_string($go) || $go !== '') {
			echo 'window.location="'.$go."\";";
		} elseif(is_array($go)){
			echo "window.location=\"?".http_build_query($go)."\";";
		} else {
			echo "window.location=document.referrer;";
		}
		echo '</script></body></html>';
		exit;
	}
	/**
	 * 给出js-Close提示并关闭页面.
	 */
	public function jsClose($msg = '', $charset='utf-8')
	{
		$this->initOutPut();
		echo "<html><head><meta charset=\"{$charset}\" /></head><body>";
		echo "<script type=\"text/javascript\">";
		$msg = $this->toJsVar($msg, '"', false);
		if($msg != ''){
			echo "alert({$msg});";
		}
		echo "window.close();";
		echo '</script></body></html>';
		exit;
	}
	
	/**
	 * 终止执行.
	 */
	public function quit($msg = null)
	{
		$this->initOutPut();
		if($msg != null){
			exit(strval($msg));
		}
		exit;
	}

	public function initOutPut($charset="utf-8", $mini='text/html'){
		if(!$this->headerSent()){
			while(ob_get_level() > 0){
				ob_end_clean();
			}
			header("Content-type:{$mini};charset={$charset}");
		}
	}

	public function forceDownload($filename, &$data, $mime='application/octet-stream'){
		if(!$this->headerSent(true)){
			$filesize = strlen($data);
			while(ob_get_level() > 0){
				ob_end_clean();
			}
			$encoded_filename = str_replace("+", "%20", urlencode($filename));
			header('Content-Type: '.$mime);
			header('Cache-Control: private, no-cache');
			header('Content-Disposition: attachment; filename="'.$filename.'"');
			header('Content-Transfer-Encoding: binary');
			header('Content-Length: '.$filesize);
			echo $data;
			exit;
		}

	}

	public function headerSent($trigger_error = false){
		//$filename, $linenum 引用模式，不用先赋值
		if (headers_sent($filename, $linenum)) {
			if($trigger_error == true){
				trigger_error("Header content has been sent in {$filename} on line {$linenum}!", E_USER_ERROR);
			}
			return true;
		}else{
			return false;
		}
	}


	/**
	 * 设置浏览器缓存当前页面的时效.
	 */
	public function cache($seconds = 3600)
	{
		if(!$this->headerSent(true)){
			header('Last-Modified: '.gmdate('D, d M Y H:i:s', time()).' GMT');
			header('Cache-Control: max-age='.$seconds);
			header('Cache-Control: private, must-revalidate');
		}
	}

	/**
	 * 禁止浏览器缓存当前页面.
	 */
	public function noCache()
	{
		if(!$this->headerSent(true)){
			header('Cache-Control: no-store');
		}
	}

	/**
	 * 将PHP变量的值嵌入在js代码中，转义引号，换行等，使其成为合法的js字面量.
	 */
	public function toJsVar($val, $strQuote = '"', $is_html_env = true)
	{
		if (is_numeric($val)) {
			return $val;
		} elseif (is_string($val)) {
			$val = str_replace('\\', '\\\\', $val); //转移自身，同时确保‘&,\b,\f’不被错误的转义
			$val = str_replace('"', '\\"', $val); //双引号
			$val = str_replace("'", '\\\'', $val); //单引号
			$val = str_replace("\t", '\\t', $val); //水平制表
			$val = str_replace("\n", '\\n', $val); //换行
			$val = str_replace("\r", '\\r', $val); //回车
			if(true == $is_html_env){
				return htmlspecialchars($strQuote.$val.$strQuote);
			}
			return $strQuote.$val.$strQuote;
		} elseif (is_bool($val)) {
			return $val == true ? 'true' : 'false';
		} else if (is_array($val)){
			return json_encode($val);
		}

		return 'null';
	}

	/**
	 * 用正则表达式判断密码强度
	 * 返回值0：密码长度不足，其他代表含有大小写、数字、及特殊字符等这四类的个数.
	 */
	public function checkSecurity($pwd, $min = 6, $max = 20)
	{
		$match = array();
		preg_match('/^(?:([a-z])|([A-Z])|([0-9])|(\\W)){'.$min.','.$max.'}|(.*?)$/', $pwd, $match);
		if (count($match) == 6) {
			return 0;
		} else {
			$counter = 0;
			for ($i = 1;$i < count($match);++$i) {
				if ($match[$i] !== '') {
					++$counter;
				}
			}

			return $counter;
		}
	}

	/**
	 * 修正explode返回结果.
	 */
	public function explode($sp, $str)
	{
		if ($str === '' || $str === null) {
			return array();
		}

		return explode($sp, $str);
	}

	/**
	 * 修正implode，允许空值.
	 */
	public function implode($sp, $arr)
	{
		if ($arr === '' || $arr === null) {
			return '';
		}

		return implode($sp, $arr);
	}
	
	/**修正默认值1970问题*/
	public function date($pattern, $unixtime = null){
		if(func_num_args() == 1){
			return date($pattern);
		}
		if($unixtime === null || !is_numeric($unixtime)){
			return $unixtime;
		}
		return date($pattern, $unixtime);
	}

	/**
	 * 针对外部输入，将变量中特殊字符转义.
	 */
	public function addSlash($data, $force = false)
	{
		// PHP 5.4 起 magic_quotes_gpc 始终返回 FALSE
		// 如果已经进行魔术引号转义就直接返回数据（非强制模式）
		if (get_magic_quotes_gpc() && $force == false) {
			return $data;
		}
		if (is_array($data)) {
			foreach ($data as $key => &$item) {
				$data[$key] = $this->{__FUNCTION__}($item, $force);
			}

			return $data;
		}

		return addslashes($data);
	}

	/**
	 * 针对外部输入，将变量中经过转义的特殊字符反转义.
	 */
	public function stripSlash($data, $force = false)
	{
		// PHP 5.4 起 magic_quotes_gpc 始终返回 FALSE
		if ($force == true || get_magic_quotes_gpc()) {
			if (is_array($data)) {
				foreach ($data as $key => &$val) {
					$data[$key] = $this->{__FUNCTION__}($val, $force);
				}

				return $data;
			}

			return stripslashes($data);
		}

		return $data;
	}

	/**
	 * 移除全局变量的影响 only for PHP 5.2.
	 */
	public function clearGlobal()
	{
		if (!ini_get('register_globals')) {
			return;
		}
		if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
			trigger_error('GLOBALS overwrite attempt detected!!', E_USER_ERROR);
		}
		$except = array('GLOBALS', '_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
		$_SESSION = isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array();
		$input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES, $_SESSION);
		foreach ($input as $k => $v) {
			if (!in_array($k, $except) && isset($GLOBALS[$k])) {
				unset($GLOBALS[$k]);
			}
		}
	}

	/**
	 * 在utf-8的字符编码的字符串中截取部分，本函数适用于全部字符.
	 */
	public function subUtf8($str, $len, $pad = null)
	{
		$offset = 0;
		$chars = 0;
		$rst = null;
		$flag = array(0x3F, 0x1F, 0xF, 0x7, 0x3, 0x0);
		while ($chars < $len && $offset < strlen($str)) {
			$high = ord(substr($str, $offset, 1));
			$last = $offset;
			for ($i = 2; $i < 8; ++$i) {
				if ($high >> $i == $flag[$i - 2]) {
					$rst .= substr($str, $offset, 8 - $i);
					$offset = $offset + 8 - $i;
					++$chars;
					break;
				}
			}
			//防止非UTF-8编码导致死循环
			if ($last === $offset) {
				trigger_error('Invalid utf-8 code!!', E_USER_ERROR);
			}
		}

		return $rst == $str ? $rst : $rst.$pad;
	}
	/**
	 * 将utf8编码的数据打散为单个字符集合
	 */
	public function getUtf8Chars($str)
	{
		$offset = 0;
		$ret = array();
		$flag = array(0x3F, 0x1F, 0xF, 0x7, 0x3, 0x0);
		while ($offset < strlen($str)) {
			$high = ord(substr($str, $offset, 1));
			$last = $offset;
			for ($i = 2; $i < 8; ++$i) {
				if ($high >> $i == $flag[$i - 2]) {
					$ret[] = substr($str, $offset, 8 - $i);
					$offset = $offset + 8 - $i;
					break;
				}
			}
			//防止非UTF-8编码导致死循环
			if ($last === $offset) {
				trigger_error('Invalid utf-8 code!!', E_USER_ERROR);
			}
		}

		return $ret;
	}
	/**
	 * 返回一个精确到微秒数时间戳（最近几年内14位最够了）
	 */
	public function getMicroTime(){
		//末尾补零凑齐14位 eg:1516082380
		return substr(str_replace('.', '', microtime(true)).'00000',0,14);
	}

	/**
	 * 将Excel日期转换为unixtime
	 * Excel日期是从1900到现在的天数
	 * unixtime是从1970到现在的秒数
	 */
	public function timeFromExcel($val, $zone = 8)
	{
		return intval(($val-25569)*86400-$zone*3600);
	}

	/**
	 * 将unixtime转换为Excel日期
	 * Excel日期是从1900到现在的天数
	 * unixtime是从1970到现在的秒数
	 */
	public function timetoExcel($unix, $zone = 8)
	{
		return ($unix+$zone*3600)/86400+25569;
	}

	/**
	 * 隐藏字符串的部分，如电话，身份证等，适用于ASCII字符组成的字符串.
	 */
	public function textPartHide($text, $dchar = '*', $index = 0.48)
	{
		$text_len = strlen($text);
		$hide_len = ceil($text_len *  $index); 
		$start = ceil($text_len -  $hide_len)/2;
		
		$replace = str_repeat($dchar, $hide_len);
		return substr_replace($text, $replace, $start, $hide_len);
	}

	/**
	 * 获取客户端浏览器的IP地址.
	 */
	public function getIPAddr()
	{
		static $ipAddr = null;
		if($ipAddr === null){
			$addrInfo = array($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['REMOTE_ADDR']);

			$hex = '[0-9A-F]{1,4}';
			$pattern_v4 = "/(((25[0-5]|2[0-4]\\d|1?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|1?\\d\\d?))/";
			$pattern_v6 = "/((($hex:){7}($hex|:))|(($hex:){6}(:$hex|:))|(($hex:){5}(((:$hex){1,2})|:))|(($hex:){4}(((:$hex){1,3})|:))|(($hex:){3}(((:$hex){1,4})|:))|(($hex:){2}(((:$hex){1,5})|:))|(($hex:){1}(((:$hex){1,6})|:))|(:(:$hex){1,7}))/i";

			foreach($addrInfo as $addr){
				if(preg_match($pattern_v6, $addr, $match)){
					$ipAddr = $match[1];
					break;
				}
				if(preg_match($pattern_v4, $addr, $match)){
					$ipAddr = $match[1];
					break;
				}
			}
		}

		return $ipAddr == null ? '0.0.0.0' : $ipAddr;
	}

	/**
	 * 生成指定长度的随机字符串（由ASCII字符构成），通常用作干扰盐值.
	 */
	public function randStr($length, $nacs='na')
	{
		static $nacsSet=array(
			'n' => '0123456789',
			'a' =>  'abcdefghijklmnopqrstuvwxyz',
			'c' =>  'ABCDEFGHIGKLMNOPQRSTUVWXYZ',
			's' => '_,.?!@#^$()%+*/='
		);
		$charSet='';
		$ret = '';
		for($i=0; $i<strlen($nacs); $i++) {
			$index = $nacs[$i];
			$charSet .= $nacsSet[$index];
		}
		for ($i = 0; $i < $length; ++$i) {
			$index = rand(0, strlen($charSet));
			$ret .= $charSet[$index];
		}

		return $ret;
	}

	/**
	 * 随机生成gb2312中的一个一级汉字（16-55区，每区94个汉字，即常用汉字）
	 * 其中第55区有5个空位是：D7FA-D7FE，需要跳过
	 */
	public function randCNChar($charset = 'UTF-8'){
		$high = rand(0xB0, 0xD7);
		$low = ($high == 0xD7)? rand(0xA1, 0xF9) : rand(0xA1, 0xFE);
		$dbChar = chr($high) . chr($low);
		return iconv('GB2312', $charset, $dbChar);
	}

	/**
	 * 返回身份证号前17位的校验值
	 */
	private function getCnIDCheckCode($idCard)
	{
		$W = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1);
		$A = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
		$s = 0;
		for ($i = 0; $i < 17; ++$i) {
			$s = $s + substr($idCard, $i, 1) * $W [$i];
		}

		return $A [$s % 11];
	}

	/**
	 * 将15位身份证号转换为18位.
	 */
	public function formatCnID($idCard)
	{
		if (15 != strlen($idCard)) {
			return $idCard;
		}
		//若身份证顺序码是996 997 998 999，说明是为百岁老人准备的特殊码
		if (array_search(substr($idCard, 12, 3), array('996', '997', '998', '999')) !== false) {
			$idCardBase = substr($idCard, 0, 6).'18'.substr($idCard, 6);
		} else {
			$idCardBase = substr($idCard, 0, 6).'19'.substr($idCard, 6);
		}

		return $idCardBase .= $this->getCnIDCheckCode($idCardBase);
	}

	/**
	 * 验证18位的身份证号.
	 */
	public function checkCnID($idCard, $convert = false)
	{
		if ($convert && strlen($idCard) == 15) {
			return $this->formatCnID($idCard);
		}
		if (strlen($idCard) != 18) {
			return false;
		}
		$idCardBase = substr($idCard, 0, 17);
		$checkCode = substr($idCard, 17, 1);
		if ($this->getCnIDCheckCode($idCardBase) != strtoupper($checkCode)) {
			return false;
		}

		return true;
	}

	/**格式化日期格式，支持xls时间*/
	public function formatDate($str, $maxYear = 50){
		if(preg_match('/(\\d{4})[\\-\\/\\.]+(\\d{1,2})[\\-\\/\\.]+(\\d{1,2})/',$str, $match)){
			$str = $match[1] . '-' . sprintf('%02d', $match[2]) . '-' . sprintf('%02d', $match[3]);
		}else if(preg_match('/(\\d{2})\\/(\\d{2})\\/(\\d{2}|\\d{4})/', $str, $match)){
			if(strlen($match[3]) == 2){
				if(intval($match[3]) > $maxYear){ // 年份后两位大于 50 已经足够了
					return '19' . $match[3] . '-' . $match[1] . '-' . $match[2];
				}else{
					return '20' . $match[3] . '-' . $match[1] . '-' . $match[2];
				}
			}else{
				return $match[3].'-'.$match[1].'-'.$match[2];
			}
		}else if(is_int($str)){
			return date('Y-m-d', ($str-19-70*365)*86400);
		}
		return null;
	}
	
	/*
	* 根据 https://en.wikipedia.org/wiki/ISO_8601 定义，
	* 一年的1月4号必在第一周 ，可求取相对第一周过去的天数，
	* 进而求取在1月4号后过去weeks-1周的某一个日期，
	* 最后可获得通过这个日期获得所在周的信息
	*/
	public function getWeekInfo($weekth, $year){
		
		$add_days = ($weekth - 1) * 7;
		$that_time = strtotime("+{$add_days} day {$year}-01-04");
		
		return $this->getWeekInfoByUnixTime($that_time);
	}
	
	/**
	* 通过一个时间戳获得所在周的信息
	*/
	public function getWeekInfoByUnixTime($unixtime){
		$retData = array();
		
		$retData['week_start'] = strtotime('this week monday', $unixtime);
		$retData['week_end'] = strtotime('this week sunday', $unixtime);
		$retData['weekth'] = intval(date('W', $unixtime));
		$retData['year'] = date('Y', $unixtime);
		if(date('m', $unixtime) == '12' && $retData['weekth'] == 1){
			$retData['year'] = $retData['year'] + 1;
		}
		
		return $retData;
	}
	
	

	/**
	 * money frome digit convert to cn.
	 */
	public function rmbFormat($money = 0, $is_round = true, $int_unit = '元')
	{
		$chs = array(0, '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖');
		$uni = array('', '拾', '佰', '仟');
		$dec_uni = array('角', '分');
		$exp = array('', '万', '亿');
		$res = '';
		$parts = explode('.', $money, 2);
		$int = isset($parts [0]) ? strval($parts [0]) : 0;
		$dec = isset($parts [1]) ? strval($parts [1]) : '';
		$dec_len = strlen($dec);
		if (isset($parts [1]) && $dec_len > 2) {
			$dec = $is_round ? substr(strrchr(strval(round(floatval('0.'.$dec), 2)), '.'), 1) : substr($parts [1], 0, 2);
		}
		if (empty($int) && empty($dec)) {
			return '零';
		}
		for ($i = strlen($int) - 1, $t = 0; $i >= 0; ++$t) {
			$str = '';
			for ($j = 0; $j < 4 && $i >= 0; $j++, $i--) {
				$u = $int{$i}
				> 0 ? $uni [$j] : '';
				$str = $chs [$int {$i}].$u.$str;
			}
			$str = rtrim($str, '0');
			$str = preg_replace('/0+/', '零', $str);
			$u2 = $str != '' ? $exp [$t] : '';
			$res = $str.$u2.$res;
		}
		$dec = rtrim($dec, '0');
		if (!empty($dec)) {
			$res .= $int_unit;
			$cnt = strlen($dec);
			for ($i = 0; $i < $cnt; ++$i) {
				$u = $dec {$i}
				> 0 ? $dec_uni [$i] : '';
				$res .= $chs [$dec {$i}].$u;
			}
			if ($cnt == 1) {
				$res .= '整';
			}
			$res = rtrim($res, '0');
			$res = preg_replace('/0+/', '零', $res);
		} else {
			$res .= $int_unit.'整';
		}

		return $res;
	}

	/**
	 * 适用于window系统(zip里的目录不能含有多字节字符)
	 */
	public function zipExtractTo($zipfile, $dest){
		$dest = rtrim($dest, DS).DS;
		$zip = new ZipArchive();
		if ($zip->open($zipfile)){
			for($i=0; $i < $zip->numFiles; $i++){
				$entry = $zip->getNameIndex($i);
				if (preg_match("/[\\x7f-\\xff]/", $entry)) {
					throw new Exception('Unable to read a dir which named width multibyte encodings!');
				}
				if(substr($entry, -1) == '/'){
					$abs_entry = $dest . $entry;
					if(!file_exists($abs_entry)){
						mkdir($abs_entry, 0640, true);
					}
					continue;
				}
				$fp = $zip->getStream($entry);
				$ofp = fopen($dest . $entry, 'w');
				if ($fp === false){
					throw new Exception('Unable to extract the file.');
				}
				while(!feof($fp) ){
					fwrite( $ofp, fread($fp, 8192));
				}
				fclose($fp);
				fclose($ofp);
			}
			return $zip->close();
		}
		throw new Exception('Could not open archive.');
	}

	/**
	 * 获取pathinfo模式下的get变量，形式如：/key1/val1/key2/val2/...
	 */
	public function urlRewrite($pathinfo = null)
	{
		$uri = $pathinfo ? $pathinfo : $_SERVER['PATH_INFO'];
		$uri =  iconv('GBK', 'UTF-8', $uri);
		$params = explode('/', trim($uri, '/'));
		$ret = array();
		for ($i = 0;$i < count($params);$i += 2) {
			$ret[$params[$i]] = $params[$i + 1];
		}

		return $ret;
	}

	/**
	 * 将文件尺寸转换为更适合人们阅读的形式
	 * @param  long $bytes 文件字节数
	 * @return string        带单位的文件长度
	 */
	public function toHumanSize($bytes) {
		$units = 'BKMGTP';
		$factor = floor((strlen($bytes) - 1) / 3);
		$unit = ($units[$factor] == 'B')? 'B' : $units[$factor] . 'B';
		return sprintf("%.2f", $bytes / pow(1024, $factor)) . $unit;
	}

	public function paseHumanSize($size){
		$size = str_replace('B','',strtoupper($size));
		if(preg_match('/^([0-9.]+)([KMGTP])$/', $size, $match)){
			$pos = strpos('BKMGTP', $match[2]);
			$size = $match[1] * pow(2, 10 * $pos);
		}
		return $size;
	}
	/**
	 * 模拟http数据包获取远程的某个页面的html代码(简单版)
	 * 受Session影响，访问系统自身，本函数可能会发生阻塞，请避免Session死锁.
	 */
	public function simpleHttpRequest($url, $postData = array(), $cookieData = array())
	{
		$cookie = null;
		foreach ($cookieData as $key => $val) {
			$cookie .= "{$key}={$val};";
		}
		$header = "User-Agent: Mozilla/5.0+ " . CRLF;
		if ($cookie != null) {
			$header = 'Cookie: '.rtrim($cookie, ';') . CRLF;
		}
		$streamopt = array(
			'http' => array(
				'method' => empty($postData) ? 'GET' : 'POST',
				'header' => $header . "Accept: */* " . CRLF,
				'content' => http_build_query($postData),
				'timeout' => 30,
			),
		);

		return @file_get_contents($url, false, stream_context_create($streamopt));
	}
	
	/**
	 * 将文件下载到服务器
	 * @param  string $url      下载链接
	 * @param  string $category 下载路径
	 * @param  array  $allowExt 允许的扩展名
	 * @return array           返回下载信息
	 */
	public function serverDownload ($url, $category = 'utility', $allowExt = array(), $maxSize = '30MB'){
		$maxSize = $this->paseHumanSize($maxSize);
		if (empty($allowExt)) {
			$allowExt = array('gif', 'jpg', 'png', 'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'flv', 'mp4');
		}
		
		$path = parse_url($url, PHP_URL_PATH);
		$pathInfo = pathinfo($path);
		
		$header= get_headers($url, true);
		if ($header['Content-Length'] > $maxSize) {
			return array('error' => '1', 'link' => $url, 'message' => "下载失败，文件大小限制{$maxSize}Byte！");
		}
		
		if (!in_array($pathInfo['extension'], $allowExt)) {
			return array('error' => '1', 'link' => $url, 'message' => '下载失败，文件格式不符合要求！');
		}
		
		$microTimeSet = explode(' ', microtime());
		$microTimeSet[0] = str_replace('0.', '', $microTimeSet[0]);
		$filename = date('dHis-',$microTimeSet[1]).$microTimeSet[0].rand(10, 99).'.'.$pathInfo['extension'];
		if(strpos($category, '..') !== false){
			return array('error' => '1', 'link' => $url, 'message' => "存储路径不能含有“..”！");
		}
		if(!file_exists(UPLOAD_PATH) || !is_writable(UPLOAD_PATH)){
			return array('error' => '1', 'field' => $field, 'message' => '下载失败，存储目录不存在或无写权限！');
		}
		$cat_path = preg_replace('/[\\\\\\/]/',DS, trim($category,'\\/'));
		$cat_uri = str_replace(DS, '/', $cat_path);
		$filedir = UPLOAD_PATH.$cat_path.DS.date('Y').DS.date('m').DS;
		$uri = $cat_uri.date('/Y/m/').$filename;
		$file = $filedir.$filename;
		if (!file_exists($filedir) && !$this->mkdir($filedir, 0640, true)) {
			return array('error' => '1', 'link' => $url, 'message' => '下载失败，系统权限不足，无法创建目录！');
		}

		file_put_contents($file, file_get_contents($url));
		return array(
			'error' => '0',
			'uri' => $uri,
			'file' => $file,
			'origin' =>$pathInfo['filename'],
			'size' => $header['Content-Length']
		);
	}

	/**
	 * 模拟http协议的post请求支持文件(简单版)
	 * 受Session影响，访问系统自身，本函数可能会发生阻塞，请避免Session死锁.
	 */
	public function MultiHttpPost($url, $post, $files = array(), $cookies = array())
	{
		$content = null;
		$cookie = null;
		$boundary = '----MultiHttpPostBoundary0kAhjgdI5g4';
		//post-data
		foreach ($post as $key => &$val) {
			$content .= '--' . $boundary . CRLF;
			$content .= "Content-Disposition: form-data; name=\"{$key}\"" . CRLF . CRLF;
			$content .= $val . CRLF;
		}

		//file-data
		foreach ($files as $key => &$file) {
			$content .= '--' . $boundary . CRLF;
			$fileContents = file_get_contents($file);
			$content .= "Content-Disposition: form-data; name=\"{$key}\"; filename=\"{$file}\"" . CRLF;
			$ext = substr(strrchr($file, '.'), 1);
			$mime = MimeSet::getMime($ext);
			$content .= "Content-Type: {$mime}" . CRLF;
			$content .= "Content-Transfer-Encoding: binary" . CRLF . CRLF;
			$content .= $fileContents . CRLF;
		}
		$content .= '--' . $boundary . '--' . CRLF;
		foreach ($cookies as $key => &$val) {
			$cookie .= "{$key}={$val};";
		}
		$header = "User-Agent: Mozilla/5.0+ " . CRLF;
		if ($cookie != null) {
			$header .= 'Cookie: '.rtrim($cookie, ';'). CRLF;
		}
		$header .= "Content-Type: multipart/form-data; boundary={$boundary}" . CRLF;
		$streamContext = stream_context_create(array('http' => array(
			'method' => 'POST',
			'header' => $header,
			'content' => $content,
		)));
		$handler = @fopen($url, 'rb', false, $streamContext);
		if (false === $handler) {
			throw new Exception("There is a problem with '{$url}'");
		}
		$response = @stream_get_contents($handler);
		if (false === $response) {
			throw new Exception("Problem reading data from {$url}");
		} else {
			fclose($handler);
		}

		return $response;
	}

	/**
	 * 上传单文件函数
	 * 返回的错误代码（1:自定义错误, 2:没有上传文件, 3:NET & I/O错误, 4:不存在的上传动作）.
	 */
	public function upload($field, $category = 'utility', $allowExt = array(), $maxSize = '30MB')
	{
		$maxSize = $this->paseHumanSize($maxSize);
		if (empty($allowExt)) {
			$allowExt = array('gif', 'jpg', 'png', 'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'flv', 'mp4');
		}
		if (isset($_FILES[$field])) {
			if ($_FILES[$field]['error'] == 4) {
				return array('error' => '2', 'field' => $field, 'message' => '没有上传文件，请选择文件再上传！');
			} elseif ($_FILES[$field]['error'] > 0) {
				return array('error' => '3', 'field' => $field, 'message' => '上传失败，错误代码：'.$_FILES[$field]['error'].'，请联系管理员！');
			}
			$fileSize = $_FILES[$field]['size'];
			if ($fileSize > $maxSize) {
				return array('error' => '1', 'field' => $field, 'message' => "上传失败，文件大小限制{$maxSize}Byte！");
			}
			if (!is_uploaded_file($_FILES[$field]['tmp_name'])) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，非法的临时文件，谢绝攻击！');
			}
			$originName = $_FILES[$field]['name'];
			$fileExt = strtolower(substr(strrchr($originName, '.'), 1));
			if (!in_array($fileExt, $allowExt)) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，文件格式不符合要求！');
			}
			$microTimeSet = explode(' ', microtime());
			$microTimeSet[0] = str_replace('0.', '', $microTimeSet[0]);
			$filename = date('ymdHis-',$microTimeSet[1]).$microTimeSet[0].rand(100, 999).'.'.$fileExt;
			if(strpos($category, '..') !== false){
				return array('error' => '1', 'field' => $field, 'message' => "存储路径不能含有“..”！");
			}
			if(!file_exists(UPLOAD_PATH) || !is_writable(UPLOAD_PATH)){
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，上传目录不存在或无写权限！');
			}
			$cat_path = preg_replace('/[\\\\\\/]/',DS, trim($category,'\\/'));
			$cat_uri = str_replace(DS, '/', $cat_path);
			$filedir = UPLOAD_PATH.$cat_path.DS;
			$uri = $cat_uri.'/'.$filename;
			$file = $filedir.$filename;
			if (!file_exists($filedir) && !$this->mkdir($filedir, 0640, true)) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，系统权限不足，无法创建目录！');
			}
			if (file_exists($file) || !move_uploaded_file($_FILES[$field]['tmp_name'], $file)) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，文件名已存在或文件写入出错！');
			};

			return array(
				'error' => '0',
				'field' => $field,
				'uri' => $uri,
				'file' => $file,
				'origin' =>$originName,
				'size' => $fileSize,
				'ext' => $fileExt
			 );
		}

		return array('error' => '4', 'message' => '不存在上传动作！');
	}

	/**
	* 上传单文件到指定的位置函数
	* 返回的错误代码（1:自定义错误, 2:没有上传文件, 3:NET & I/O错误, 4:不存在的上传动作）.	 */
	public function uploadTo($field, $file, $maxSize = '30MB')
	{
		$allowedExt = strtolower(substr(strrchr($file, '.'), 1));
		$maxSize = $this->paseHumanSize($maxSize);
		if (isset($_FILES[$field])) {
			if ($_FILES[$field]['error'] == 4) {
				return array('error' => '2', 'field' => $field, 'message' => '没有上传文件，请选择文件再上传！');
			} elseif ($_FILES[$field]['error'] > 0) {
				return array('error' => '3', 'field' => $field, 'message' => '上传失败，错误代码：'.$_FILES[$field]['error'].'，请联系管理员！');
			}
			$fileSize = $_FILES[$field]['size'];
			if ($fileSize > $maxSize) {
				return array('error' => '1', 'field' => $field, 'message' => "上传失败，文件大小限制{$maxSize}Byte！");
			}
			if (!is_uploaded_file($_FILES[$field]['tmp_name'])) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，非法的临时文件，谢绝攻击！');
			}
			$originName = $_FILES[$field]['name'];
			$fileExt = strtolower(substr(strrchr($originName, '.'), 1));
			if ($fileExt != $allowedExt) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，请上传指定的文件类型！');
			}
			$filedir = dirname($file);
			if (!file_exists($filedir) && !$this->mkdir($filedir, 0640, true)) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，系统权限不足，无法创建目录！');
			}
			if (!move_uploaded_file($_FILES[$field]['tmp_name'], $file)) {
				return array('error' => '1', 'field' => $field, 'message' => '上传失败，文件写入出错！');
			};

			return array(
				'error' => '0',
				'field' => $field,
				'file' => $file,
				'origin' =>$originName,
				'size' => $fileSize
			 );
		}

		return array('error' => '4', 'message' => '不存在上传动作！');
	}

	/**
	* 上传动作不确定上传数目的情况下，直接上传全部或有前缀的部分.
	* 本函数不支持数组形式的表单如photo[]等形式,请使用formatUploadStruct修正，再上传.
	*/
	public function uploadAll($prefix = null, $category = 'images', $allow = array(), $maxSize = 5242880)
	{
		$ret = array();
		foreach ($_FILES as $key => &$val) {
			if ($prefix == null || ($prefix != null && strpos($key, $prefix) === 0)) {
				if(is_array($val['name'])){
					trigger_error("Field-style:XXX[] is unsupported by uploadAll!", E_USER_ERROR);
				}
				$ret[$key] = $this->upload($key, $category, $allow, $maxSize);
			}
		}

		return $ret;
	}

	/**
	 * 本函数将数组形式的表单字段如photo[]等形式强制转换为｛photo_0,photo_1,...photo_n｝等前缀模式
	 * 转换后可使用单独使用uploadAll()指定前缀完成上传.其 $_FILES 转换前为
	 * $_FILES = array(
	 *   'photo' => array (
	 *     'name' => array (
	 *       0 => 'photo1.png',
	 *       1 => 'photo2.jpg',
	 *     ),
	 *     'type' => array (
	 *       0 => 'image/png',
	 *       1 => 'image/jpg',
	 *      ),
	 *      ...,
	 *   )
	 * )
	 * 其 $_FILES 转换后为
	 * $_FILES = array(
	 *   'photo_0' => array (
	 *     'name' => 'photo1.png',
	 *     'type' =>'image/png',
	 *     ...,
	 *   ),
	 *   'photo_1' => array (
	 *     'name' => 'photo2.jpg',
	 *     'type' =>'image/jpg',
	 *     ...,
	 *   ),
	 * )
	 */
	public function multiUpload($field, $category = 'utility', $allow = array(), $maxSize = 31457280)
	{
		if ($_FILES[$field] != null && is_array($_FILES[$field]['name'])) {
			$count = count($_FILES[$field]['name']);
			$files = array();
			for ($i = 0;$i < $count;++$i) {
				$field_ext = $field.'_'.$i;
				if($_FILES[$field_ext] != null){
					trigger_error("\$_FILES can\'t be rewritten, key '$field_ext' aready exists!", E_USER_ERROR);
				}
				foreach ($_FILES[$field] as $key => &$item) {
					$files[$field_ext][$key] = $item[$i];
				}
			}
			unset($_FILES[$field]);
			$_FILES = array_merge($_FILES, $files);

			return $this->uploadAll($field, $category, $allow, $maxSize);
		}else if($_FILES[$field] != null){
			trigger_error('上传失败，必须是XXX[]的形式!', E_USER_ERROR);
		}else{
			return array();
		}
	}
	/**
	 * 本函数为multiUpload的单独转换部分
	 * @return ovid
	 */

	public function formatUploadStruct(){
		$files = array();
		foreach($_FILES as $field => $_file){
			if(is_array($_file['name'])){
				$count = count($_file['name']);
				for ($i = 0;$i < $count;++$i) {
					$field_ext = $field.'_'.$i;
					if($_FILES[$field_ext] !=null){
						trigger_error("\$_FILES can\'t be rewritten, key '$field_ext' aready exists!", E_USER_ERROR);
					}
					$files[$field_ext] = array();
					foreach ($_file as $key => $val) {
						$files[$field_ext][$key] = $val[$i];
					}
				}
			}else{
				$files[$field] = $_file;
			}
		}
		$_FILES = $files;
	}

	/**
	 * 生成缩略图(即时尺寸不变也会进行像素采样，减小尺寸大小)，
	 * 本函数支持透明通道，等比例缩放，对小图片不作处理.
	 *
	 * @param width 目标最大宽度
	 * @param height 目标最大高度
	 */
	public function thumb($src, $dst = null, $only_compress = false, $width = 1440, $height = 900)
	{
		if (!file_exists($src)) {
			trigger_error('Invalid file path!!', E_USER_ERROR);
		}
		$ext = strtolower(substr(strrchr($src, '.'), 1));
		$origin = array('gif', 'jpeg', 'jpg', 'png', 'bmp');
		if (!in_array($ext, $origin)) {
			trigger_error('Unsupported image file!!', E_USER_ERROR);
		}

		switch ($ext) {
			case 'bmp':
				$img = imagecreatefromwbmp($src);
				break;
			case 'gif':
				$img = imagecreatefromgif($src);
				break;
			case 'jpeg':
			case 'jpg':
				$img = imagecreatefromjpeg($src);
				break;
			case 'png':
				$img = imagecreatefrompng($src);
				break;
		}

		list($w, $h) = getimagesize($src);
		if($only_compress == false){
			// 只进行缩小操作
			$ratio = min(1, $width / $w, $height / $h);
			$width = $w * $ratio;
			$height = $h * $ratio;
		}

		$canvas = imagecreatetruecolor($width, $height);
		if ($ext == 'gif' or $ext == 'png') {
			imagecolortransparent($canvas, imagecolorallocatealpha($canvas, 0, 0, 0, 127));
			imagealphablending($canvas, false);
			imagesavealpha($canvas, true);
		}
		imagecopyresampled($canvas, $img, 0, 0, 0, 0, $width, $height, $w, $h);
		$dst = ($dst == null)? $src : $dst;
		switch ($ext) {
			case 'bmp':
				return imagewbmp($canvas, $dst);
			case 'gif':
				return imagegif($canvas, $dst);
			case 'jpeg':
			case 'jpg':
				return imagejpeg($canvas, $dst);
			case 'png':
				return imagepng($canvas, $dst);
		}
	}

	/**
	 * 生成灰度图片.
	 *
	 * @return bool 是否成功
	 */
	public function imageGray($src, $dst)
	{
		if (!file_exists($src)) {
			trigger_error('Invalid file path!!', E_USER_ERROR);
		}
		$ext = strtolower(substr(strrchr($src, '.'), 1));
		$origin = array('gif', 'jpeg', 'jpg', 'png', 'bmp');
		if (!in_array($ext, $origin)) {
			trigger_error('Unsupported image file!!', E_USER_ERROR);
		}
		switch ($ext) {
			case 'bmp':
				$img = imagecreatefromwbmp($src);
				break;
			case 'gif':
				$img = imagecreatefromgif($src);
				break;
			case 'jpeg':
			case 'jpg':
				$img = imagecreatefromjpeg($src);
				break;
			case 'png':
				$img = imagecreatefrompng($src);
				break;
			default:
				return false;
		}
		if (imageistruecolor($img)) {
			imagetruecolortopalette($img, false, 256); //如果是真彩色图象，将真彩色图像转换为调色板图像
		}
		for ($i = 0; $i < imagecolorstotal($img); ++$i) { /*获得调色板中颜色的数目*/
			$rgb = imagecolorsforindex($img, $i); //获得颜色i点的颜色值
			$gray = round(0.229 * $rgb['red'] + 0.587 * $rgb['green'] + 0.114 * $rgb['blue']); //获得颜色灰度值
			imagecolorset($img, $i, $gray, $gray, $gray); //设置i点颜色值
		}
		switch ($ext) {
			case 'bmp':
				return imagewbmp($img, $dst);
			case 'gif':
					return imagegif($img, $dst);
			case 'jpeg':
			case 'jpg':
				return imagejpeg($img, $dst);
			case 'png':
				return imagepng($img, $dst);
			default:
				return false;
		}
	}

	/**
	 * 返回当前文件夹中的文件名及子文件夹名
	 */
	public function fetchDir($path, $encoding = 'UTF-8')
	{
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		$path = rtrim(preg_replace('/\\\\\\//', DS, $path), DS) . DS;
		$retval = array('folder'=>array(), 'file'=>array());
		$handler = opendir($path);
		while (false !== ($dir = readdir($handler))) {
			if ($dir != '.' && $dir != '..') {
				$file = $path . $dir;
				if (is_dir($file)) {
					$retval['folder'][] = array('name'=>$dir, 'path'=>$file);
				} else if (is_file($file)) {
					$retval['file'][] = array('name'=>$dir, 'path'=>$file);
				}
			}
		}
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			foreach($retval as $key =>$item){
				foreach($item as $sub_key =>$item2){
					foreach($item2 as $sub_key2 =>$val){
						$retval[$key][$sub_key][$sub_key2] = mb_convert_encoding($val, 'UTF-8', 'GB2312');
					}
				}
			}
		}
		closedir($handler);

		return $retval;
	}

	/**
	 * 递归删除文件夹及其内容和子文件夹
	 */
	public function removeDir($path, $encoding = 'UTF-8')
	{
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		$path = rtrim(preg_replace('/\\\\\\//', DS, $path), DS) . DS;
		$handler = opendir($path);
		while (false !== ($dir = readdir($handler))) {
			if ($dir != '.' && $dir != '..') {
				$file = $path . $dir;
				if (is_dir($file)) {
					$this->{__FUNCTION__}($file);
				} elseif (is_file($file)) {
					unlink($file);
				}
			}
		}
		closedir($handler);
		rmdir($path);
	}
	public function filectime($path, $encoding = 'UTF-8'){
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		return filectime($path);
	}
	public function filesize($path, $encoding = 'UTF-8'){
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		return filesize($path);
	}
	public function mkdir($path, $mode = 064, $recursive = true, $encoding = 'UTF-8'){
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		return mkdir($path, $mode, $recursive);
	}
	public function file_exists($path, $encoding = 'UTF-8'){
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		return file_exists($path);
	}
	public function is_file($path, $encoding = 'UTF-8'){
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		return is_file($path);
	}
	public function unlink($path, $encoding = 'UTF-8'){
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		return unlink($path);
	}
	public function file_get_contents($path, $encoding = 'UTF-8'){
		if(php_uname('s')=='Windows NT'
			&& version_compare(PHP_VERSION, '7.1.0') < 0
			&& $encoding == 'UTF-8'){
			$path = mb_convert_encoding($path, 'GB2312', 'UTF-8');
		}
		return file_get_contents($path);
	}
	
	/**将数据保存成csv文件（给什么存什么）*/
	public function dataToCsv($data, $fileName, $encoding ='GBK'){
		$retVal = '';
		foreach($data as $record){
			foreach ($record as $key => &$value) {
				//有特殊字符的数据用双引号包裹，双引号自身用双引号转义
				$record[$key]= str_replace('"', '""', $value);
				if(strpos($record[$key], ',') !== false
				|| strpos($record[$key], CRLF) !== false
				|| strpos($record[$key], '"') !== false){
					$record[$key] = '"' . $record[$key] . '"';
				}
			}
			$retVal .= implode(',', $record) . CRLF;
		}
		if(strtoupper($encoding) != 'UTF-8'){
			$retVal = mb_convert_encoding($retVal, $encoding, "UTF-8");
		}
		$this->forceDownload($fileName, $retVal);
	}
	
	/**将数据保存成csv文件（使用第一行作为列参考，过滤掉其他行多余的数据）*/
	public function dataToCsvFixed($data, $fileName, $encoding ='GBK'){
		$retVal = '';
		$colSet = is_array($data[0])?array_keys($data[0]):array();
		foreach($data as $record){
			$_tmp = array();
			foreach ($colSet as $key) {
				//有特殊字符的数据用双引号包裹，双引号自身用双引号转义
				$_tmp[$key]= str_replace('"', '""', $record[$key]);
				if(strpos($_tmp[$key], ',') !== false 
				|| strpos($_tmp[$key], CRLF) !== false
				|| strpos($_tmp[$key], '"') !== false){
					$_tmp[$key] = '"' . $_tmp[$key] . '"';
				}
			}
			$retVal .= implode(',', $_tmp) . CRLF;
		}
		if(strtoupper($encoding) != 'UTF-8'){
			$retVal = mb_convert_encoding($retVal, $encoding, "UTF-8");
		}
		$this->forceDownload($fileName, $retVal);
	}
	/*
	* 读取csv文件到一个二维数组
	* csv要求使用逗号来分隔内容，使用双引号包裹换行及逗号等特殊字符！
	* utf-8编码方式不与ascii码发生冲突，所以直接进行字节搜索是安全的
	* utf-8 1字节字符编码 0xxxxxxx（即ascii）
	* utf-8 2字节字符编码 110xxxxx 10xxxxxx
	* utf-8 3字节字符编码 1110xxxx 10xxxxxx 10xxxxxx
	* ...
	*/
	public function dataFromCsv($str, $encoding = 'GBK'){
		$quotes = 0;
		// 返回的数据（二维数组）
		$retData = array();
		// csv文件中的一行数据
		$record = array();
		// csv中的一个元素中的字符
		$queue = array();
		if(strtoupper($encoding) != 'UTF-8'){
			$str = mb_convert_encoding($str, "UTF-8", $encoding);
		}
		$len = strlen($str);
		for($i=0; $i < $len; $i++){
			$char = $str[$i];
			$char_next = '';
			// 获取下一个字符
			if($i+1 < $len){
				$char_next = $str[$i+1];
			}
			// 碰到一个逗号怎么处理
			if($char == ','){
				// 逗号在引号对外面如："xxx""", 或 "xxxxx", 或 xx1,xx2
				if($quotes % 2 == 0){
					$val = implode('', $queue);
					if($quotes > 0){
						$val = substr($val, 1, strlen($val)-2);
					}
					// 反转义内容里面的引号，并存储
					$record[] = str_replace('""', '"', $val);
					$queue = array();
					$quotes = 0;
					// 假如文件末尾没有CRLF，且最后一个字符是逗号
					if($i+1 == $len){
						$record[] = '';
						$retData[] = $record;
					}
				}else{
					// 此逗号属于正文部分
					$queue[] = ',';
				}
			}else if($char . $char_next == CRLF){
				// 同理，CRLF处理也要看换行符在不在引号里面
				if($quotes % 2 == 0){
					$val = implode('', $queue);
					if($quotes > 0){
						$val = substr($val, 1, strlen($val)-2);
					}
					// 反转义内容里面的引号，并存储
					$record[] = str_replace('""', '"', $val);
					$queue = array();
					// 结束一行并开始新的行（数组）
					$retData[] = $record;
					$record = array();
					$quotes = 0;
					// 最后，最后两个字符是CRLF是正常的不需要特殊处理
				}else{
					// 此换行符属于正文部分
					$queue[] = CRLF;
				}
				// 索引向前推进一位（CRLF由两个字符构成）
				$i++;
			}else{
				// 引号计数加一
				if($char == '"'){
					$quotes++;
				}
				$queue[] = $char;
				// 假如文件末尾没有CRLF，遍历到最后一个字符应：
				if($i+1 == $len){
					$val = implode('', $queue);
					// 最后一个字符是引号才有必要检测
					if($char == '"'){
						$val = substr($val, 1, strlen($val)-2);
					}
					$record[] = str_replace('""', '"', $val);
					$retData[] = $record;
				}
			}
		}

		return $retData;
	}
	
	/**将表格数据根据表头转换成关联数组（二维）*/
	public function parseTableToAssoc(&$fromData, $ColMap){
		$colUnfindSet = array();
		$colKeyToIndexMap = array();
		$retData = array();
		foreach($ColMap as  $colKey => &$colName){
			$found = false;
			for($colIndex = 0; $colIndex < count($fromData[0]); ++$colIndex) {
				$cellVal = trim($fromData[0][$colIndex]);
				if($cellVal == $colName || $cellVal == $colKey){
					$colKeyToIndexMap[$colKey] = $colIndex;
					$found = true;
					break;
				}
			}
			if($found == false){
				$colUnfindSet[] = $colName;
			}
		}
		if(count($colUnfindSet)>0){
			$colUnfindStr=implode('、', $colUnfindSet);
			return array('error'=>1, 'message'=>'第一行未找到列：'.$colUnfindStr, 'data'=>null);
		}
		// 舍弃第一行（第一行为列标题，无实际意义）
		for($rowIndex = 1; $rowIndex < count($fromData); ++$rowIndex) {
			$record = array();
			foreach($colKeyToIndexMap as $colKey => $mapIndex) {
				$record[$colKey] = $fromData[$rowIndex][$mapIndex];
			}
			$retData[] = $record;
		}
		return array('error'=>0, 'message'=>'转换成功', 'data'=>$retData);
	}

	/**
	 * 返回包含指定键值集合的部分数组，$reverse反选.
	 */
	public function subSet($array, $keySet, $reverse = false)
	{
		$rtn = array();
		foreach ($keySet as &$key) {
			if (array_key_exists($key, $array)) {
				$rtn[$key] = $array[$key];
			}
		}
		if ($reverse) {
			return array_diff_key($array, $rtn);
		}

		return $rtn;
	}

	/**
	* 将一个一维数组放到一个二维数组中
	*/
	public function arrayMerge($data, $record){
		array_push($data,$record);

		return $data;
	}

	/**
	 * 将从数据库中返回的多行数据(二维数组)按字段（列）求和，返回一维数组.
	 */
	public function multiSum($data)
	{
		$retData=array();
		foreach ($data as &$record) {
			foreach ($record as $key => &$val) {
				$retData[$key] += $val;
			}
		}

		return $retData;
	}

	/**
	 * 将多次数据查询的结果集（已合并）去掉重复记录,使用主键ID进行判断.
	 */
	public function multiUnique($data, $primaryKey = 'id')
	{
		$keySet = array();
		foreach ($data as $index => &$record) {
			if (in_array($record[$primaryKey], $keySet)) {
				unset($data[$index]);
				continue;
			}
			array_push($keySet, $record[$primaryKey]);
		}

		return $data;
	}

	/**
	 * 使用array_multisort对二维数组排序，适用于多次数据查询合并的结果集
	 * 在使用时应确保记录无重复，建议使用$this->multiUnique过滤.
	 */
	public function multiSort($data, $key, $order = SORT_ASC, $type = SORT_NUMERIC)
	{
		$keySet = array();
		foreach ($data as &$record) {
			$keySet[] = $record[$key];
		}
		array_multisort($keySet, $order, $type, $data);

		return $data;
	}

	/**
	 * 将二维数组的的一列剥离出来，并进行矩阵转置.
	 * 自php5.5起可使用array_column函数代替
	 */
	public function arrayColumn($data, $valCol, $indexCol= null)
	{
		$ret = array();
		foreach ($data as $rowKey => &$rowData) {
			$key= ($indexCol !== null)? $rowData[$indexCol] : $rowKey;
			$ret[$key] = $rowData[$valCol];
		}

		return $ret;
	}
	
	/**
	 * 返回二维数组指定的列，并转换为一维数组如有键值重复的数据则求和
	 */
	public function arrayColumnSum($data, $valCol, $indexCol)
	{
		$ret = array();
		foreach ($data as $rowKey => &$rowData) {
			$index = $rowData[$indexCol];
			$ret[$index] = $ret[$index] + $rowData[$valCol];
		}

		return $ret;
	}
	
	/**将数据插入到数组的第一个位置（支持键值）*/
	public function arrayUnshiftAssoc($arr, $val, $key = null){
		if($key === null){
			array_unshift($arr, $val);
			return $arr;
		}
		$reverse = array_reverse($arr, true);
		$reverse[$key] = $val;
		return array_reverse($reverse, true);
	}
	public function arrayRand($data, $num = 1){
		shuffle($data);
		$result = array();
		$max = count($data);
		for ($i = 0; $i < $num && $i<$max; $i++) {
			$result[] = $data[$i];
		}
		if($max < $num){
			for ($i = 0; $i < $num-$max; $i++) {
				$result[] = null;
			}
		}
		return $result;
	}


	/**
	 * 将二维数组的一列进行转换（把数组data的col列里面的值替换成与map里键对应的值）.
	 * $data = array('a'=>array('b'=>'c'));
	 * $map = array('c'=>'d');
	 * return array('a'=>array('b'=>'d'));
	 */
	public function dataMap($data, $col, $map)
	{
		foreach ($data as $rowKey => &$rowData) {
			if (array_key_exists($rowData[$col], $map)) {
				$key = $rowData[$col];
				$data[$rowKey][$col] = $map[$key];
			}
		}

		return $data;
	}

	/**
	 * 将一维数组的值进行转换（把数组data里面的值替换成与map里键对应的值）.
	 * $data = array('a'=>'b');
	 * $map = array('b'=>'c');
	 * return array('a'=>'c');
	 */
	public function arrayMap($data, $mapData)
	{
		foreach ($data as $key => &$refMapkey) {
			if (array_key_exists($refMapkey, $mapData)) {
				$data[$key] = $mapData[$refMapkey];
			}
		}

		return $data;
	}
	
	/**
	 * 用$data中$dataKey对应的值获取该值在$map中键对应的值
	 */
	public function arrayMapVal($dataKey, $map, $data){
		$mapKey = $data[$dataKey];
		return $map[$mapKey];
	}

	/**
	 * 矩阵转置操作.
	 */
	public function transposeMatrix($matrix)
	{
		$ret = array();
		foreach ($matrix as $i => &$row) {
			foreach ($row as $j => &$cell) {
				$ret[$j][$i] = $cell;
			}
		}

		return $ret;
	}

	/**
	 * 同trim类似的功能但本系列函数仅删除匹配的边界一次
	 *
	 * @param string $string    被清理的字符串
	 * @param string $delimiter 要清理的字符串
	 *
	 * @return string 返回后的字符串
	 */
	public function trimOne($string, $delimiter)
	{
		$tmp = $this->ltrimOne($string, $delimiter);

		return $this->rtrimOne($tmp, $delimiter);
	}

	public function rtrimOne($string, $delimiter)
	{
		$string = trim($string);
		$length = strlen($delimiter);
		if (substr($string, strlen($string) - $length, $length) == $delimiter) {
			return substr($string, 0, strlen($string) - $length);
		}

		return $string;
	}
	public function ltrimOne($string, $delimiter)
	{
		$string = trim($string);
		$length = strlen($delimiter);
		if (substr($string, 0, $length) == $delimiter) {
			return substr($string, $length);
		}

		return $string;
	}

	/**
	 * 将键值对形式的数据转换成地址形式：key=val&key2=val2&...
	 * 本函数附加一个安全校验码
	 */
	public function dataToUrl($data, $data2 = array())
	{
		// 理论上键值不可能重复，值重复正常现象
		foreach ($data2 as $key => &$val) {
			$data[$key] = $val;
		}

		return http_build_query($data);
	}

	/**
	 * 判断是否在集合中，仅用于sql数字集合.
	 */
	public function isInSet($needle, $mixedSet){
		if(!is_array($mixedSet)){
			$mixedSet = preg_split('/\\||，|、|,|\\s/', $mixedSet);
		}

		return in_array($needle, $mixedSet);
	}
	
	/**
	 * 格式化数据用于MySQL in子句(内容中的引号已经转义).
	 */
	function fixInStr($data, $split = ',', $emptyVal = '-1'){
		if($data === null || $data === array()){
			return $emptyVal;
		}
		if(is_scalar($data)){
			$data = $this->explode($split, $data);
		}
		foreach($data as $key => $val){
			$val = ($val === null)?'' : $val;
			$data[$key] = '\'' . str_replace('\'', '\'\'', $val) . '\'';
		}
		return $this->implode(',', $data);
	}

	/**
	 * 为使用sql的in结构格式化数据
	 * @param  string $str             输入值
	 * @param  string $nullPlaceHolder 为空时的占位符
	 * @return string                  格式化后的值
	 */
	public function sqlInSetFormat($str, $nullPlaceHolder = '-1'){
		if($str === null || $str === ''){
			$str = $nullPlaceHolder;
		}
		$mixedSet = preg_split('/\\||，|、|,|\\s/', $str);
		return implode(',', $mixedSet);
	}

	/**
	 * 将一个url的quergString部分解析为键值对数组.
	 */
	public function parseQuery($url)
	{
		$tmp = array();
		$info = parse_url($url);
		parse_str($info['query'], $tmp);

		return $tmp;
	}

	/**
	 * form 表单单行本输入框.
	 */
	public function formTextInput($name, $cat, $val = null, $inputRawAttr = null)
	{
		static $serialId = 0;
		++$serialId;

		if (!preg_match('/\\sid=\\s*/is', $inputRawAttr)) {
			$inputRawAttr .= " id=\"frm_{$name}_{$serialId}\"";
		}
		if ($val !== null && is_array($val)) {
			$key_name = str_replace('[]', '', $name);
			$val = ($key_name == '') ? '' : $val[$key_name];
		}
		if ($name != null) {
			$inputRawAttr .= " name=\"{$name}\"";
		}
		if(strtolower(trim($cat)) == 'file'){

			return "<input type=\"{$cat}\" name=\"{$name}\" data-val=\"{$val}\" {$inputRawAttr} />";
		}

		return "<input type=\"{$cat}\" value=\"{$val}\" {$inputRawAttr} />";
	}

	/**
	 * form 表单多行文本输入区.
	 */
	public function formTextArea($name, $val = null, $textAreaRawAttr = null)
	{
		static $serialId = 0;
		++$serialId;
		if (!preg_match('/\\sid=\\s*/is', $textAreaRawAttr)) {
			$textAreaRawAttr .= " id=\"frm_{$name}_{$serialId}\"";
		}
		if ($val !== null && is_array($val)) {
			$key_name = str_replace('[]', '', $name);
			$val = ($key_name == '') ? '' : $val[$key_name];
		}
		if ($name != null) {
			$textAreaRawAttr .= " name=\"{$name}\"";
		}
		$val = htmlspecialchars($val);

		return "<textarea {$textAreaRawAttr}>{$val}</textarea>";
	}

	/**
	 * form 表单下拉列表.
	 */
	public function formSelect($name, $data, $selectedVal = null, $selectRawAttr = null, $optRawAttr = null)
	{
		static $serialId = 0;
		++$serialId;
		if (!preg_match('/\\sid=\\s*/is', $selectRawAttr)) {
			$selectRawAttr .= " id=\"frm_{$name}_{$serialId}\"";
		}
		if ($selectedVal !== null && is_array($selectedVal)) {
			$key_name = str_replace('[]', '', $name);
			$selectedVal = ($key_name == '') ? '' : $selectedVal[$key_name];
		}
		if ($name != null) {
			$selectRawAttr .= " name=\"{$name}\"";
		}
		$formStr = "<select {$selectRawAttr}>" . CRLF;
		// 如果集合中没有否定值，则给预设一个
		if($data[''] === null && $data[0] === null){
			$data = $this->arrayUnshiftAssoc($data, '请选择...', '');
		}
		foreach ($data as $key => &$val) {
			$key = htmlspecialchars($key);
			if(is_array($val)){
				$formStr .= "<optgroup label=\"{$key}\">" . CRLF;
				foreach ($val as $real_key => &$real_val) {
					$select = (strval($real_key) === strval($selectedVal)) ? 'selected' : null;
					$real_key = htmlspecialchars($real_key);
					$real_val = strip_tags($real_val);
					$real_val = htmlspecialchars($real_val);
					$formStr .= "<option {$select} value=\"{$real_key}\" {$optRawAttr}>{$real_val}</option>" . CRLF;
				}
				$formStr .= "</optgroup>" . CRLF;
			 }else{
				$select = (strval($key) === strval($selectedVal)) ? 'selected' : null;
				$val = strip_tags($val);
				$val = htmlspecialchars($val);
				$formStr .= "<option {$select} value=\"{$key}\" {$optRawAttr}>{$val}</option>" . CRLF;
			}

		}

		return $formStr.'</select>';
	}

	/**
	 * form 表单单选按钮组.
	 */
	public function formRadio($name, $data, $checkedVal = null, $radioRawAttr = null, $labelRawAttr = null)
	{
		static $serialId = 0;
		$formStr = null;
		foreach ($data as $key => &$label) {
			++$serialId;
			$radioRawAttr_item = $radioRawAttr;
			$labelRawAttr_item = $labelRawAttr;
			$has_id=preg_match('/\\sid=\\s*/is', $radioRawAttr);
			if (!$has_id) {
				$radioRawAttr_item .= " id=\"frm_{$name}_{$serialId}\"";
			}
			if (!$has_id && !preg_match('/\\sfor=/is', $labelRawAttr)) {
				$labelRawAttr_item .= " for=\"frm_{$name}_{$serialId}\"";
			}

			if ($name != null) {
				$radioRawAttr_item .= " name=\"{$name}\"";
			}
			if (is_array($checkedVal)) {
				$checked = ($key == $checkedVal[$name]) ? 'checked' : null;
			} else {
				$checked = (strval($key) === strval($checkedVal)) ? 'checked' : '';
			}

			$key = htmlspecialchars($key);
			$formStr .= "<input type=\"radio\" {$checked} value=\"{$key}\" {$radioRawAttr_item} /><label {$labelRawAttr_item} > {$label} </label>&nbsp;";
		}

		return preg_replace('/^(.+?)(?:&nbsp;)*$/', '${1}', $formStr);
	}

	/**
	 * form 表单复选框组.注意第三个参数同其他form函数不一样.
	 */
	public function formCheckBox($name, $data, $set = array(), $boxRawAttr = null, $labelRawAttr = null)
	{
		static $serialId = 0;
		$formStr = null;
		$set = is_array($set)? $set : $this->explode(',', $set);
		foreach ($data as $key => &$label) {
			++$serialId;
			$boxRawAttr_item = $boxRawAttr;
			$labelRawAttr_item = $labelRawAttr;
			$has_id=preg_match('/\\sid=\\s*/is', $boxRawAttr);
			if (!$has_id) {
				$boxRawAttr_item .= " id=\"frm_{$name}_{$serialId}\"";
			}
			if (!$has_id && !preg_match('/\\sfor=/is', $labelRawAttr)) {
				$labelRawAttr_item .= " for=\"frm_{$name}_{$serialId}\"";
			}
			if ($name != null) {
				$boxRawAttr_item .= " name=\"{$name}[]\"";
			}
			$checked = (in_array($key, $set)) ? 'checked' : null;
			$key = htmlspecialchars($key);
			$formStr .= "<input type=\"checkbox\" {$checked} value=\"{$key}\" {$boxRawAttr_item} /><label {$labelRawAttr_item} > {$label} </label>&nbsp;";
		}

		return preg_replace('/^(.+?)(?:&nbsp;)*$/', '${1}', $formStr);
	}

	/**
	 * 像一颗树一样，使表单列表Select元素有层次感
	 * array(array(id=>xx,pid=>xx,title=>xx, optAttr),...).
	 * optAttr 的用例 如有子节点的禁用(即给有子节点的记录加 optAttr=>'disabled')
	 */
	public function selectTree(&$data, $root, $sid, $parent = array(), $end = false)
	{
		$subSet = array();
		$decorate = null;
		$retStr = null;
		$current = null;
		foreach ($data as &$item) {
			if ($item['id'] == $root) {
				$current = $item;
			}
			if ($item['pid'] == $root) {
				$subSet[] = $item;
			}
		}
		$subSetSize = count($subSet);
		if ($current != null) {
			for ($i = 0; $i < count($parent) - 1; ++$i) {
				if ($parent[$i] != 0) {
					$decorate .= '│&nbsp;';
				} else {
					$decorate .= '&nbsp;&nbsp;';
				}
			}
			if (count($parent) > 0) {
				$decorate .= $end ? '└─' : '├─';
			}
			$selected = $current['id'] == $sid ? 'selected' : null;
			$retStr .= "<option {$selected} {$current['optAttr']} value=\"{$current['id']}\">{$decorate}{$current['title']}</option>" . CRLF;
			array_push($parent, 1);
			for ($i = 0; $i < $subSetSize; ++$i) {
				$end = ($i == $subSetSize - 1) ? true : false;
				if (true == $end) {
					$parent[count($parent) - 1] = 0;
				}
				$retStr .= $this->{__FUNCTION__}($data, $subSet[$i]['id'], $sid, $parent, $end);
			}
		} else {
			for ($i = 0; $i < $subSetSize; ++$i) {
				$retStr .= $this->{__FUNCTION__}($data, $subSet[$i]['id'], $sid);
			}
		}

		return $retStr;
	}

	/**
	 * 像一颗树一样，使分类列表项有层次感，不支持分页
	 * 输入：array(array('id'=>xx,'pid'=>xx,'title'=>xx),...).
	 * 输出：array(array('id'=>xx,'pid'=>xx,'title'=>xx,'decorate'=>xx,'childCount'=>xx,'treeDepth'=>xx),...).
	 */
	public function catTree(&$data, $root, $parent = array(), $end = false)
	{
		$subSet = array();
		$decorate = null;
		$ret = array();
		$current = null;
		foreach ($data as &$item) {
			if ($item['id'] == $root) {
				$current = $item;
			}
			if ($item['pid'] == $root) {
				$subSet[] = $item;
			}
		}
		$subSetSize = count($subSet);
		if ($current != null) {
			for ($i = 0; $i < count($parent) - 1; ++$i) {
				if ($parent[$i] != 0) {
					$decorate .= '│&nbsp;&nbsp;&nbsp;';
				} else {
					$decorate .= '&nbsp;&nbsp;&nbsp;&nbsp;';
				}
			}
			if (count($parent) > 0) {
				$decorate .= $end ? '└─' : '├─';
			}
			$current['decorate'] = $decorate;
			$current['childCount'] = $subSetSize;
			$current['treeDepth'] = count($parent);
			$ret[] = array_merge($ret, $current);

			array_push($parent, 1);
			for ($i = 0; $i < $subSetSize; ++$i) {
				$end = ($i == $subSetSize - 1) ? true : false;
				if (true == $end) {
					$parent[count($parent) - 1] = 0;
				}
				$tmp = $this->{__FUNCTION__}($data, $subSet[$i]['id'], $parent, $end);
				$ret = array_merge($ret, $tmp);
			}
		} else {
			for ($i = 0; $i < $subSetSize; ++$i) {
				$tmp = $this->{__FUNCTION__}($data, $subSet[$i]['id']);
				$ret = array_merge($ret, $tmp);
			}
		}

		return $ret;
	}

	/**
	 * 调用程序在后台执行,请注意系统环境变量.
	 *
	 * @param string $cmd cmd命令
	 *
	 * @return bool 是否成功
	 */
	public function execInBackground($cmd)
	{
		if (substr(php_uname(), 0, 7) == 'Windows') {

			return pclose(popen('start /B '.$cmd, 'r'));
		}

		return exec($cmd.' > /dev/null &');
	}


	/**
	 * php7.1+
	 * 自PHP7.1起使用如下加密替代方法（libmcrypt自2007年不再维护）
	 */
	public function openssl_encrypt($encrypt, $key, $method = 'AES-128-CBC'){
		// 被支持的加密方式可用如下的方式获取：
		// $methodSet = openssl_get_cipher_methods();
		// 获取加密方式需要的iv长度：openssl_cipher_iv_length('AES-128-CBC');
		// 生成加密所需要的iv串值：openssl_random_pseudo_bytes($length);
		$iv_length = openssl_cipher_iv_length($method);
		$iv_bytes = openssl_random_pseudo_bytes($iv_length);
		$result = openssl_encrypt($encrypt, $method, $key, OPENSSL_RAW_DATA, $iv_bytes);

		return base64_encode(json_encode(array(
			'result'=>base64_encode($result),
			'iv'=>base64_encode($iv_bytes)
		)));
	}

	/**
	 * php7.1+
	 * 自PHP7.1起使用如下解密替代方法（libmcrypt自2007年不再维护）
	 */
	public function openssl_decrypt($decrypt, $key, $method = 'AES-128-CBC'){
		$decrypt = json_decode(base64_decode($decrypt), true);
		$iv_bytes = base64_decode($decrypt['iv']);
		$result = base64_decode($decrypt['result']);
		return openssl_decrypt($result, $method, $key, OPENSSL_RAW_DATA, $iv_bytes);
	}

	public function crc16($string) {
		$crc = 0xffff;
		for ($x = 0; $x < strlen($string); $x++) {
			$crc = $crc ^ ord($string[$x]);
			for ($y = 0; $y < 8; $y++) {
				if (($crc & 0x1) == 0x1) {
					$crc = (($crc >> 1) ^ 0xA001);
				} else {
					$crc = $crc >> 1;
				}
			}
		}
		return $crc;
	}

	public function crc8($string){
		$crc = 0;
		$tmp = 0;
		$pos = 0;
		for($pos = 0; $pos < strlen($string); $pos++){
			$tmp = $crc << 1;
			$tmp = $tmp + ord($string[$pos]);
			$crc = (($tmp & 0xff) + ($tmp >> 8)) & 0xff;
		}
		return $crc;
	}
}